package wx_article_catcher

import (
	"fmt"
	"io"
	"log"
	"net/url"
	"os"
	"path/filepath"
	"regexp"
	"strings"
	"time"
	"unicode/utf8"
	"webDownloader/engine/wx_article_catcher/utils"

	"github.com/PuerkitoBio/goquery"
	"github.com/bitly/go-simplejson"
	"github.com/thinkeridea/go-extend/exunicode/exutf8"
)

// 常量定义
const (
	maxFileNameLength = 50
)

// 正则表达式
var (
	linkPreloadReg      = regexp.MustCompile(`<link rel="modulepreload".*?>`)
	createTimeReg       = regexp.MustCompile("var createTime = '(.*?)'")
	jsDecodeTimeReg     = regexp.MustCompile(`JsDecode\('(\d{4}-\d{2}-\d{2} \d{2}:\d{2})'\)`)
	nameStartWithNumReg = regexp.MustCompile(`^\d+_`)
)

// WxArticleCleaner 用于处理微信文章的清理和格式化
type WxArticleCleaner struct {
	cssUrl        string
	jsUrl         string
	createTimeStr string
}

// NewWxArticleCleaner 创建新的文章清理器
func NewWxArticleCleaner(cssUrl, jsUrl string) *WxArticleCleaner {
	return &WxArticleCleaner{
		cssUrl: cssUrl,
		jsUrl:  jsUrl,
	}
}

// CleanArticle 清理和格式化微信文章
func (c *WxArticleCleaner) CleanArticle(html string, article *DownloadTaskWxArticle) string {
	// 提取和设置创建时间
	timeStr := c.extractAndSetCreateTime(html, article)
	if timeStr != "" {
		c.createTimeStr = timeStr
		html = c.updatePublishTime(html, timeStr)
	}

	// 清理 HTML
	cleanedHTML := c.cleanHTML(html)

	// 提取主要内容
	mainHtml := c.extractMainContent(strings.NewReader(cleanedHTML))
	if mainHtml == "" {
		return cleanedHTML
	}

	return mainHtml
}

func extractCreateTime(html string) string {
	// 首先尝试从 var createTime = '...' 中提取时间
	matches := createTimeReg.FindAllStringSubmatch(html, 1)
	if matches != nil {
		timeStr := matches[0][1]
		return timeStr
	}

	// 如果上面的正则没有匹配到，尝试从 JsDecode('...') 中提取时间
	jsMatches := jsDecodeTimeReg.FindAllStringSubmatch(html, 1)
	if jsMatches != nil {
		timeStr := jsMatches[0][1]
		return timeStr
	}

	// 如果都匹配不到
	return ""
}

// extractAndSetCreateTime 提取创建时间并设置到文章对象
func (c *WxArticleCleaner) extractAndSetCreateTime(html string, article *DownloadTaskWxArticle) string {
	timeStr := extractCreateTime(html)
	conf := article.Context.GetConfig()
	if (*conf)["from"].(string) == "article" {
		c.updateArticleTime(timeStr, article)
	}
	return timeStr
}

// updateArticleTime 更新文章时间信息
func (c *WxArticleCleaner) updateArticleTime(timeStr string, article *DownloadTaskWxArticle) {
	loc, err := time.LoadLocation("Local")
	if err != nil {
		return
	}

	t, err := time.ParseInLocation("2006-01-02 15:04", timeStr, loc)
	if err != nil {
		return
	}

	article.Date = t.Unix()
	article.FileName = t.Format("06010215_") + article.FileName
}

// updatePublishTime 更新发布时间显示
func (c *WxArticleCleaner) updatePublishTime(html, timeStr string) string {
	// 有些非 em
	return strings.Replace(html,
		"<em id=\"publish_time\" class=\"rich_media_meta rich_media_meta_text\"></em>",
		"<em id=\"publish_time\" class=\"rich_media_meta rich_media_meta_text\">"+timeStr+"</em>",
		1)
}

// cleanHTML 清理 HTML 内容
func (c *WxArticleCleaner) cleanHTML(html string) string {
	html = linkPreloadReg.ReplaceAllString(html, "")
	return strings.Replace(html, "style=\"visibility: hidden;\"", "", 1)
}

// extractMainContent 提取文章主要内容
func (c *WxArticleCleaner) extractMainContent(input io.Reader) string {
	doc, err := goquery.NewDocumentFromReader(input)
	if err != nil {
		log.Printf("解析 HTML 时出错: %v", err)
		return ""
	}

	if doc.Find("#js_article").Length() == 0 {
		log.Printf("HTML 中没有找到 id=\"js_article\" 元素")
		return ""
	}

	c.fixMissContent(doc)

	// 补上缺失的发布时间
	doc.Find("#publish_time").SetText(c.createTimeStr)

	// 移除脚本，会丢失一些内容
	c.removeScripts(doc)

	return c.generateFinalHTML(doc)
}

// fixMissContent 修复缺失的内容
func (c *WxArticleCleaner) fixMissContent(doc *goquery.Document) {
	descEle := doc.Find("#js_text_desc")
	if descEle.Length() == 0 {
		// 如果元素不存在，创建一个新的元素
		doc.Find("#js_article").
			AppendHtml("<div id=\"js_text_desc\"></div>").
			AddClass("rich_media_area_primary_inner ")
		descEle = doc.Find("#js_text_desc")
	}
	if descEle.Text() != "" {
		return
	}

	html, err := doc.Html()
	if err != nil {
		log.Printf("获取 HTML 时出错: %v", err)
		return
	}

	newContent := ""
	if matches := utils.TextContentReg.FindStringSubmatch(html); matches != nil {
		newContent = matches[1]
	}

	if newContent == "" {
		contentNew, exists := doc.Find("meta[property=\"og:description\"]").Attr("content")
		if exists {
			newContent = contentNew
		}
	}

	if newContent != "" {
		newContent = strings.NewReplacer("\\x0a", "<br>", "\\n", "<br>").Replace(newContent)
		descEle.SetHtml(newContent)
	}
}

// removeScripts 移除所有脚本标签
func (c *WxArticleCleaner) removeScripts(doc *goquery.Document) {
	doc.Find("script").Remove()
}

// generateFinalHTML 生成最终的 HTML
func (c *WxArticleCleaner) generateFinalHTML(doc *goquery.Document) string {
	var builder strings.Builder

	// 写入 HTML 头部
	builder.WriteString("<!DOCTYPE html><html><head>")
	builder.WriteString("<meta charset=\"UTF-8\">")
	builder.WriteString("<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">")
	builder.WriteString("<meta name=\"referrer\" content=\"never\">")

	// 写入 meta 标签
	c.writeMetaTags(doc, &builder)

	// 写入标题
	title := strings.TrimSpace(doc.Find("title").Text())
	if title == "" {
		titleNew, exists := doc.Find("meta[property=\"og:title\"]").Attr("content")
		if exists {
			// 变短一点
			title = FormatFileName(titleNew, 0)
		}
	}
	builder.WriteString(fmt.Sprintf("<title>%s</title>", title))

	// 写入自定义 CSS
	if c.cssUrl != "" {
		builder.WriteString(fmt.Sprintf("<link rel=\"stylesheet\" href=\"%s\">", c.cssUrl))
	}

	builder.WriteString("</head><body>\n")

	// 写入文章内容
	c.writeArticleContent(doc, &builder)

	// 写入自定义 JS
	if c.jsUrl != "" {
		builder.WriteString(fmt.Sprintf("<script src=\"%s\"></script>", c.jsUrl))
	}

	builder.WriteString("\n</body></html>")
	allHtml := builder.String()
	return allHtml
}

// writeMetaTags 写入 meta 标签
func (c *WxArticleCleaner) writeMetaTags(doc *goquery.Document, builder *strings.Builder) {
	doc.Find("meta").Each(func(i int, s *goquery.Selection) {
		if prop, exists := s.Attr("property"); exists && strings.HasPrefix(prop, "og:") {
			if html, err := goquery.OuterHtml(s); err == nil {
				builder.WriteString(html)
			}
		}
	})
}

// writeArticleContent 写入文章内容
func (c *WxArticleCleaner) writeArticleContent(doc *goquery.Document, builder *strings.Builder) {
	doc.Find("#js_article").Each(func(i int, s *goquery.Selection) {
		// 移除不需要的元素
		s.Find(`
			#js_tags_preview_toast,
			#js_temp_bottom_area,
			#sg_tj,
			#js_pc_qr_code,
			#wx_stream_article_slide_tip,
			.weui-hidden_abs,
			#js_share_content_page_hd
		`).Remove()

		if html, err := goquery.OuterHtml(s); err == nil {
			builder.WriteString(html)
		}
	})
}

// FileExists 检查文件是否存在
func FileExists(article *DownloadTaskWxArticle, rootDir string) bool {
	if !nameStartWithNumReg.MatchString(article.FileName) {
		article.FileName = FormatFileName(article.FileName, article.Date)
	}

	return fileExistsWithExtension(rootDir, article.FileName, ".html") ||
		fileExistsWithExtension(rootDir, article.FileName, ".html.gz")
}

// FormatFileName 格式化文件名
func FormatFileName(fileName string, date int64) string {
	// 处理文件名长度
	fileName = strings.Split(fileName, "\\n")[0]
	if utf8.RuneCountInString(fileName) > maxFileNameLength {
		fileName = exutf8.RuneSubString(fileName, 0, maxFileNameLength)
	}

	// 清理非法字符
	fileName = utils.IllegalChar1.ReplaceAllString(fileName, "_")
	fileName = utils.IllegalChar2.ReplaceAllString(fileName, "")

	if date == 0 {
		return fileName
	}

	// 添加日期前缀
	t := time.Unix(date, 0)
	return t.Format("06010215_") + fileName
}

// fileExistsWithExtension 检查指定扩展名的文件是否存在
func fileExistsWithExtension(rootDir, fileName, ext string) bool {
	filePath := filepath.Join(rootDir, fileName+ext)
	if fileInfo, err := os.Stat(filePath); err == nil && fileInfo != nil {
		return true
	}
	return false
}

// LoadConfig 加载配置文件
func LoadConfig(conf *map[string]interface{}) {
	urlStr := (*conf)["refUrl"].(string)
	if biz, err := extractBizFromURL(urlStr, "__biz"); err == nil {
		UpdateBizConfig(conf, biz)
	}

	if savePath, ok := (*conf)["saveRootPath"].(string); ok {
		if config, err := readConfigFile(savePath); err == nil {
			(*conf)["myCssPath"] = config.Get("mycss").MustString()
			(*conf)["myJsPath"] = config.Get("myjs").MustString()
		}
	}
}

// 根据 biz 参数更新 savePath
func UpdateBizConfig(conf *map[string]any, biz string) {
	savePath := (*conf)["savePath"].(string)
	if config, err := readConfigFile(savePath); err == nil {
		for k, arr := range config.Get("biz").MustMap() {
			if k == biz {
				if author, ok := arr.([]any)[0].(string); ok {
					fullPath := filepath.Join(savePath, author)
					if _, err := os.Stat(fullPath); os.IsNotExist(err) {
						os.MkdirAll(fullPath, 0755)
					}
					(*conf)["savePath"] = fullPath
				}
				break
			}
		}
	}
}

// readConfigFile 加载配置文件
func readConfigFile(savePath string) (*simplejson.Json, error) {
	file, err := os.ReadFile(filepath.Join(savePath, "config.json"))
	if err != nil {
		return nil, err
	}

	return simplejson.NewJson(file)
}

// extractBizFromURL 从 URL 中获取参数
func extractBizFromURL(urlStr, key string) (string, error) {
	u, err := url.Parse(urlStr)
	if err != nil {
		return "", err
	}
	return u.Query().Get(key), nil
}

// 从HTML内容中提取biz参数
func ExtractBizFromHTML(html string) string {
	matches := utils.BizArticleReg.FindStringSubmatch(html)
	if len(matches) > 1 {
		// 优先使用第一个捕获组的结果
		for i := 1; i < len(matches); i++ {
			if matches[i] != "" {
				biz := matches[i]
				// 验证提取的biz格式是否正确
				if utils.ValidBizPattern.MatchString(biz) {
					return biz
				}
			}
		}
	}
	return ""
}
